/*
 * The common_int.c file implements the general interrupt service routine 
 * that is called by all interrupts.
 */

#include <types.h>
#include <interrupts.h>
#include <i386_arch.h>  
#include <i386_pmode.h>  
#include <pic.h>
#include <klib.h>  // printf()
#include <panic.h> // panic() ~ temp, because we'll handle interrupts somewhere else
#include <syscall.h>
#include <globals.h>
#include <scheduler.h>

// Interrupt Service Routine (for IRQs)
//typedef void (*isr_t)(void);

struct intr_e {	
  char *msg;
  int soft_int;	
};

static struct intr_e intr_tbl[] =
{
  {  "Divide error.", 0},
  {  "Debug error.", 0},
  {  "NMI error.", 0},
  {  "Breakpoint error.", 0},
  {  "Overflow error.", 0},
  {  "BOUND Range Exceeded error.", 0},
  {  "Invalid Opcode error.", 0},
  {  "FPU Not Present error.", 0},
  {  "Double Fault error.", 0},
  {  "FPU Segment Overrun error.", 0},
  {  "Invalid TSS error.", 0},
  {  "Segment Not Present error.", 0},
  {  "Stack-Segment Fault error.", 0},
  {  "General Protection Fault error.", 0},
  {  "Page Fault error.", 0},
  {  "Unhandled interrupt.", 0},               /* number 15 */
  {  "Math Fault error.", 0},
  {  "Alignment Check error.", 0},
  {  "Machine Check error.", 0},
  {  "FPU Exception error.", 0},               /* number 19 (0x13) */
  {  "Unhandled interrupt.", 0}                /* number 20 (0x14) */
};


//void common_exception(word_t int_num, dword_t err_code)
void common_exception(uregs_t *uregs)
  {
    struct intr_e *ep;
    int idx;
    dword_t int_num,err_code;
    dword_t address,retc;  // used by page fault

    // This should never happen
    if (kernel_entering_count>1)
      {
	printf("Exception #0x%x\n",int_num);
	context_dump(uregs);
	panic("Pre-empting kernel twice.");
      }

    int_num=uregs->interrupt_number;
    //err_code=uregs->error_code;

    //printf("~~~ Exception #0x%x\n",int_num);
    //printf("~~~ Exception: #0x%x -- K.E.C.: %d ... ",int_num,kernel_entering_count);

    // If it's an IRQ, send it to the IRQ handler
    if (int_num>=IRQ00_VECTOR && int_num<=IRQ15_VECTOR)
      {
	// mask this interrupt
	//irq_disable(int_num-IRQ00_VECTOR);
	// enable other interrupts
	//enable_interrupts();
	// serve this interrupt
	handle_IRQ(uregs);

	//disable_interrupts();
	// send End Of Interrupt
	send_specific_EOI(int_num-IRQ00_VECTOR);
	//send_EOI();
	// re-enable this interrupt
	//irq_enable(int_num-IRQ00_VECTOR);

	//printf("exeunt (#0x%x -- K.E.C.: %d).\n",int_num,kernel_entering_count);
	return;
      }

    // System Call ?
    if (int_num==SYSCALL_VECTOR)
      {
	// we can re-enable interrupts if we're here
	enable_interrupts();
	// serve syscall
	demultiplex_syscall(uregs);
	//printf("exeunt (#0x%x -- K.E.C.: %d).\n",int_num,kernel_entering_count);
	//context_dump(uregs);
	return;
      }

    // If it's not an IRQ nor a syscall, then handle it here...

    idx=int_num;
    // Maybe we haven't handled it.
    // if it's not a syscall either
    if (idx>20)
      idx=20;

    ep=&intr_tbl[idx];	

    // if it's a user process do something
    // TO DO: need to implement this when the scheduler works


    // If it's not a user process... then oh my, it must be the kernel!
    // Hopefully it's never going to happen. :-)

    // print a message
    if (int_num!=PAGE_VECTOR && int_num!=DBLFAULT_VECTOR)
      {
	printf("Exception #0x%x: %s\n",int_num,ep->msg);
	printf("Error code #0x%x (seg idx=0x%x)\n",uregs->error_code,uregs->error_code>>3);
	printf("Context dump:\n");
	context_dump(uregs);
      }

    switch (int_num)
      {
      case DIVIDE_VECTOR:
	halt();
	break;	      
      case DEBUG_VECTOR:
	halt();
	break;
      case NMI_VECTOR:
	halt();
	break;
      case BREAKPOINT_VECTOR:
	printf("\n*** BREAKPOINT ***\n");
	uregs->eip++;
	//halt();
	break;
      case OVERFLOW_VECTOR:
	halt();
	break;
      case BOUND_VECTOR:
	halt();
	break;
      case OPCODE_VECTOR:
	halt();
	break;
      case NOFPU_VECTOR:
	halt();
	break;
      case DBLFAULT_VECTOR:
	emergency_message("DOUBLE FAULT");
	halt();
	break;
      case FPUOVERRUN_VECTOR:
	halt();
	break;
      case INVALIDTSS_VECTOR:
	halt();
	break;
      case SEGMENT_VECTOR:
	halt();
	break;
      case STACK_VECTOR:
	halt();
	break;
      case GPF_VECTOR:
	emergency_message("GENERAL PROTECTION FAULT");
	halt();
	break;
      case PAGE_VECTOR:	
	address=read_cr2();
	
	/*
	printf("Faulting address: 0x%x\n",address);
	printf("Context dump:\n");
	context_dump(uregs);
	*/

	// has it happened in a user process?
	// load page if appropriate
	retc=0;
	if (address < KERNEL_START)
	  retc=aout_loader(current_process,address);

	// something's wrong
	// either kernel is faulting or could not load page in user process
	if ((address >= KERNEL_START) || retc)
	  {
	    printf("Page Fault in KERNEL, halting.\n");
	    printf("Faulting address: 0x%x\n",address);
	    printf("RETC = %d\n",retc);
	    printf("Context dump:\n");
	    context_dump(uregs);
	    halt();
	  }
	break;
      case INT0x0F_VECTOR:
	halt();
	break;
      case FPU_VECTOR:
	printf("\n*** SYSTEM CHECK ***\n");
	//halt();
	break;
      case ALIGN_VECTOR:
	halt();
	break;
      case MACHINE_VECTOR:
	halt();
        break;
      case FPUPIII_VECTOR:
	halt();
	break;

      default:
	// Unhandled
	halt();
      }

    //printf("exeunt (#0x%x -- K.E.C.: %d).\n",int_num,kernel_entering_count);
  }

/*
void assign_IRQ(int irq_num,dword_t isr)
  {
    IDT_Entry_T desc;
    int vec_num;

    vec_num=irq_num+IRQ00_VECTOR;
    
    setup_IDT_Entry(&desc,IDT_TYPE_TRAP,GDT_DESC_CODE,(dword_t)isr);
    place_IDT_Entry(vec_num,&desc);

    irq_enable(irq_num);
  }


void unassign_IRQ(int irq_num)
  {
    irq_disable(irq_num);
  }
*/
